//
//  MTEDXLBuilder.m
//  XML Test
//
//  Created by jsheriff on 8/24/11.
//  Copyright 2011 The MITRE Corporation. All rights reserved.
//

#import "MTEDXLBuilder.h"
#import <libxml/xmlmemory.h>
#import "xslt.h"
#import "xsltInternals.h"
#import "transform.h"
#import "xsltutils.h"

// Private Properties and Methods

@interface MTEDXLBuilder ()

+ (xsltStylesheetPtr)createStylesheetFromBundleWithFileName:(NSString *)fileName;
+ (xmlDocPtr)createXmlDocWithDictionary:(NSDictionary *)dictionary;
+ (NSString *)formattedStringFromDate:(NSDate *)date;

@end

// Implementation

@implementation MTEDXLBuilder

static NSString * const distributionElementStylesheetFileName = @"de";
static NSString * const fieldObservationStylesheetFileName = @"fieldObservation";
static NSString * const nonXmlContentStylesheetFileName = @"nonXmlContent";
static NSString * const cotStylesheetFileName = @"cot";
static NSString * const stylesheetExtension = @"xsl";
static NSString * const dateTimeFormat = @"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'";

+ (NSString *)createDistributionElementWithId:(NSString *)distributionId
                                     senderId:(NSString *)senderId
                                 dateTimeSent:(NSDate *)dateTimeSent
                           distributionStatus:(NSString *)distributionStatus 
                                     location:(CLLocationCoordinate2D)location
                             distributionType:(NSString *)distributionType
                      combinedConfidentiality:(NSString *)combinedCondfidentiality 
                                     language:(NSString *)language 
                               contentObjects:(NSArray *)contentObjectArray {
    // Create the xml document with a root node
    xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
    xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "report");
    xmlDocSetRootElement(doc, rootNode);
    
    // Format the date
    NSString *dateTimeSentString = [self formattedStringFromDate:dateTimeSent];
    
    // Add the data to the xml document
    xmlNewChild(rootNode, NULL, BAD_CAST "distributionId", BAD_CAST [distributionId UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "senderId", BAD_CAST [senderId UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "dateTimeSent", BAD_CAST [dateTimeSentString UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "distributionStatus", BAD_CAST [distributionStatus UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "latitude", BAD_CAST [[NSString stringWithFormat:@"%f",location.latitude] UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "longitude", BAD_CAST [[NSString stringWithFormat:@"%f",location.longitude] UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "distributionType", BAD_CAST [distributionType UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "combinedConfidentiality", BAD_CAST [combinedCondfidentiality UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "language", BAD_CAST [language UTF8String]);
    for (NSString *contentObject in contentObjectArray) {
        xmlNewChild(rootNode, NULL, BAD_CAST "contentObject", BAD_CAST [contentObject UTF8String]);
    }

    // Read the stylesheet from the bundle
    xsltStylesheetPtr styleSheet = [self createStylesheetFromBundleWithFileName:distributionElementStylesheetFileName];

    // Perform the xslt transform
    xmlDocPtr resultXmlDoc = xsltApplyStylesheet(styleSheet, doc, NULL);

    // Convert the xmldoc to an NSString
    xmlChar *xmlbuff;
    int buffersize;
    xmlDocDumpFormatMemory(resultXmlDoc, &xmlbuff, &buffersize, 1);
    NSString *distributionElement = [[[NSString alloc] initWithUTF8String:(char *)xmlbuff] autorelease];
//    NSLog(@"EDXL:\n%@", distributionElement);
    xmlFree(xmlbuff);
    
    return distributionElement;
}

+ (NSString *)createFieldObservationContentObjectWithId:(NSString *)messageId
                                             srpKeyword:(NSString *)srpKeyword
                                          observationId:(NSString *)observationId
                                             preparedBy:(NSString *)preparedBy
                                             objectData:(NSDictionary *)objectDataDictionary {
    // Add the extra fields to the dictionary
    [objectDataDictionary setValue:srpKeyword forKey:@"srpKeyword"];
    [objectDataDictionary setValue:messageId forKey:@"messageId"];
    [objectDataDictionary setValue:observationId forKey:@"observationId"];
    [objectDataDictionary setValue:preparedBy forKey:@"preparedBy"];
    
    // Convert the dictionary to an xml doc
    xmlDocPtr doc = [self createXmlDocWithDictionary:objectDataDictionary];

    // Read the stylesheet from the bundle
    xsltStylesheetPtr styleSheet = [self createStylesheetFromBundleWithFileName:fieldObservationStylesheetFileName];

    // Perform the xslt transform
    xmlDocPtr resultXmlDoc = xsltApplyStylesheet(styleSheet, doc, NULL);
    
    // Convert the xmldoc to an NSString
    xmlChar *xmlbuff;
    int buffersize;
    xmlDocDumpFormatMemory(resultXmlDoc, &xmlbuff, &buffersize, 1);
    NSString *contentObject = [[[NSString alloc] initWithUTF8String:(char *)xmlbuff] autorelease];
    xmlFree(xmlbuff);
    
    // Strip the xml declaration from the content object
    NSRange range = [contentObject rangeOfString:@"<contentObject>"];
    if (range.location == NSNotFound) {
        NSLog(@"xml declaration not found");
//        NSLog(@"Field Observation Content Object:\n%@", contentObject);
        return contentObject;
    }
    
    NSString *modifiedContentObject = [contentObject substringFromIndex:range.location];
//    NSLog(@"Field Observation Content Object:\n%@", modifiedContentObject);

    return modifiedContentObject;
}

+ (NSString *)createNonXmlContentObjectWithContentDescription:(NSString *)contentDescription
                                                srpKeyword:(NSString *)srpKeyword
                                                     mimeType:(NSString *)mimeType
                                                observationId:(NSString *)observationId
                                                   preparedBy:(NSString *)preparedBy
                                                    messageId:(NSString *)messageId
                                                  contentData:(NSString *)contentData {
    
    /*
    NSMutableDictionary *objectDataDictionary = [[NSMutableDictionary alloc] init];
    // Add the extra fields to the dictionary
    [objectDataDictionary setValue:messageId forKey:@"messageId"];
    [objectDataDictionary setValue:observationId forKey:@"observationId"];
    [objectDataDictionary setValue:preparedBy forKey:@"preparedBy"];*/
    
    // Convert the dictionary to an xml doc
    //xmlDocPtr doc = [self createXmlDocWithDictionary:objectDataDictionary];
    
    // Create the xml document with a root node
    xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
    xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "content");
    xmlDocSetRootElement(doc, rootNode);
        
    // Add the data to the xml document
    xmlNewChild(rootNode, NULL, BAD_CAST "observationId", BAD_CAST [observationId UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "srpKeyword", BAD_CAST [srpKeyword UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "preparedBy", BAD_CAST [preparedBy UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "description", BAD_CAST [contentDescription UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "mimeType", BAD_CAST [mimeType UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "data", BAD_CAST [contentData UTF8String]);
    
    // Read the stylesheet from the bundle
    xsltStylesheetPtr styleSheet = [self createStylesheetFromBundleWithFileName:nonXmlContentStylesheetFileName];
    
    // Perform the xslt transform
    xmlDocPtr resultXmlDoc = xsltApplyStylesheet(styleSheet, doc, NULL);
    
    // Convert the xmldoc to an NSString
    xmlChar *xmlbuff;
    int buffersize;
    xmlDocDumpFormatMemory(resultXmlDoc, &xmlbuff, &buffersize, 1);
    NSString *contentObject = [[[NSString alloc] initWithUTF8String:(char *)xmlbuff] autorelease];
    xmlFree(xmlbuff);
    
    // Strip the xml declaration from the content object
    NSRange range = [contentObject rangeOfString:@"<contentObject>"];
    if (range.location == NSNotFound) {
        NSLog(@"xml declaration not found");
//        NSLog(@"Non XML Content Object:\n%@", contentObject);
        return contentObject;
    }
    
    NSString *modifiedContentObject = [contentObject substringFromIndex:range.location];
//    NSLog(@"Non XML Content Object:\n%@", modifiedContentObject);
    
    return modifiedContentObject;
}

+ (NSString *)createCotContentObjectWithType:(NSString *)type
                                positionKeyword:(NSString *)positionKeyword
                                srpKeyword:(NSString *)srpKeyword
                                         uid:(NSString *)uid
                                         how:(NSString *)how
                                        time:(NSDate *)time
                                       start:(NSDate *)start
                                       stale:(NSDate *)stale
                                    location:(CLLocationCoordinate2D)location {
    // Create the xml document with a root node
    xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
    xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "event");
    xmlDocSetRootElement(doc, rootNode);
    
    // Format the dates
    NSString *timeString = [self formattedStringFromDate:time];
    NSString *startString = [self formattedStringFromDate:start];
    NSString *staleString = [self formattedStringFromDate:stale];
    
    // Add the data to the xml document
    xmlNewChild(rootNode, NULL, BAD_CAST "type", BAD_CAST [type UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "posKeyword", BAD_CAST [positionKeyword UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "srpKeyword", BAD_CAST [srpKeyword UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "uid", BAD_CAST [uid UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "how", BAD_CAST [how UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "time", BAD_CAST [timeString UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "start", BAD_CAST [startString UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "stale", BAD_CAST [staleString UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "latitude", BAD_CAST [[NSString stringWithFormat:@"%f",location.latitude] UTF8String]);
    xmlNewChild(rootNode, NULL, BAD_CAST "longitude", BAD_CAST [[NSString stringWithFormat:@"%f",location.longitude] UTF8String]);
    
    // Read the stylesheet from the bundle
    xsltStylesheetPtr styleSheet = [self createStylesheetFromBundleWithFileName:cotStylesheetFileName];
    
    // Perform the xslt transform
    xmlDocPtr resultXmlDoc = xsltApplyStylesheet(styleSheet, doc, NULL);
    
    // Convert the xmldoc to an NSString
    xmlChar *xmlbuff;
    int buffersize;
    xmlDocDumpFormatMemory(resultXmlDoc, &xmlbuff, &buffersize, 1);
    NSString *contentObject = [[[NSString alloc] initWithUTF8String:(char *)xmlbuff] autorelease];
    xmlFree(xmlbuff);
    
    // Strip the xml declaration from the content object
    NSRange range = [contentObject rangeOfString:@"<contentObject>"];
    if (range.location == NSNotFound) {
        NSLog(@"xml declaration not found");
//        NSLog(@"CoT Content Object:\n%@", contentObject);
        return contentObject;
    }
    
    NSString *modifiedContentObject = [contentObject substringFromIndex:range.location];
//    NSLog(@"CoT Content Object:\n%@", modifiedContentObject);
    
    return modifiedContentObject;

}


+ (xsltStylesheetPtr)createStylesheetFromBundleWithFileName:(NSString *)fileName {
    NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:stylesheetExtension];
    NSString *string = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    
    xmlSubstituteEntitiesDefault(1);
	xmlLoadExtDtdDefaultValue = 1;
    
    xmlDocPtr styleSheetDoc = xmlParseMemory([string UTF8String], [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
    if (styleSheetDoc == NULL) {
        NSLog(@"xmlParseMemory returned NULL on string");
        return NULL;
    }
    
    xsltStylesheetPtr styleSheet = xsltParseStylesheetDoc(styleSheetDoc);
    
    return styleSheet;
}

+ (xmlDocPtr)createXmlDocWithDictionary:(NSDictionary *)dictionary {
    // Create the xml document with a root node
    xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
    xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "report");
    xmlDocSetRootElement(doc, rootNode);
    
    // Iterate over the key/value pairs in the dictionary, adding a new node directly below the root node for each key/value pair
    for (NSString *key in dictionary) {
        id object = [dictionary objectForKey:key];
        if ([object isKindOfClass:[NSString class]]) {
            NSString *stringObject = object;
            xmlNewChild(rootNode, NULL, BAD_CAST [key UTF8String], BAD_CAST [stringObject UTF8String]);
        }
        else if ([object isKindOfClass:[NSNumber class]]) {
            NSNumber *numberObject = object;
            xmlNewChild(rootNode, NULL, BAD_CAST [key UTF8String], BAD_CAST [[numberObject stringValue] UTF8String]);
        }
        else if ([object isKindOfClass:[CLLocation class]]) {
            CLLocation *location = (CLLocation *)object;
            NSNumber *latitude = [NSNumber numberWithDouble:location.coordinate.latitude];
            NSNumber *longitude = [NSNumber numberWithDouble:location.coordinate.longitude];
            xmlNodePtr locationNode = xmlNewChild(rootNode, NULL, BAD_CAST [key UTF8String], NULL);
            xmlNewChild(locationNode, NULL, BAD_CAST "latitude", BAD_CAST [[latitude stringValue] UTF8String]);
            xmlNewChild(locationNode, NULL, BAD_CAST "longitude", BAD_CAST [[longitude stringValue] UTF8String]);
        }
        else if ([object isKindOfClass:[NSDate class]]) {
            NSDate *date = (NSDate *)object;
            xmlNewChild(rootNode, NULL, BAD_CAST [key UTF8String], BAD_CAST [[self formattedStringFromDate:date] UTF8String]);
        }
    }
    
    return doc;
}

+ (NSString *)formattedStringFromDate:(NSDate *)date {
    // Format the date
    NSLocale *enUSPOSIXLocale;
    NSDateFormatter *sRFC3339DateFormatter = [[NSDateFormatter alloc] init];
    enUSPOSIXLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease];
    [sRFC3339DateFormatter setLocale:enUSPOSIXLocale];
    [sRFC3339DateFormatter setDateFormat:dateTimeFormat];
    [sRFC3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];    
    NSString *dateTimeString = [sRFC3339DateFormatter stringFromDate:date];
    [sRFC3339DateFormatter release];

    return dateTimeString;
}

@end
